#include "headers/task.h"
#include "../memory/headers/paging.h"
#include "../api/headers/string.h"
#include "headers/timer.h"
#include "../api/headers/errors.h"
#include "../adt/headers/linked_list.h"

extern u32int globalInt;

// The currently running task.
volatile task_t *current_task;
extern task_t* focused_task;

// The start of the task linked list.
//volatile task_t *ready_queue;

volatile linked_list_type* task_list;
void makeVirtualConsoleFocused(task_t* task);
// Some externs are needed to access members in paging.c...
extern page_directory_t *kernel_directory;
extern page_directory_t *current_directory;
extern u32int alloc_frame(page_t*,int,int);
extern u32int initial_esp;
extern u32int read_eip();
extern u32int timer_ticks;
extern linked_list_type *to_be_killed;
extern volatile BOOL critical_section;

resource_check_t res_check[N_OF_RES_TYPES];
static BOOL queue_not_empty(u32int queue);
static BOOL list_not_empty(u32int list);
static BOOL time_has_passed(u32int tick);

// The next available process ID.
u32int next_pid = 1;

void initialise_tasking()
{
	
    // Rather important stuff happening, no interrupts please!
#ifdef DEBUG
printf("Initializing multitasking\n"); 
#endif
    asm volatile("cli");
	
	
	setLastError(NO_ERROR);
	initialise_resource_management();
#ifdef DEBUG
	printf("Interrupts disabled\n"); 
#endif
    // Relocate the stack so we know where it is.
    move_stack((void*)STACK_LOCATION, INITIAL_STACK_SIZE);
	
	task_list = create_linked_list(true);
	if(getLastError() != NO_ERROR)
		K_PANIC("Error creating task list");
#ifdef DEBUG
	printf("Stack moved to a new location: 0x%h\n", STACK_LOCATION);
#endif
    // Initialise the first task (kernel task)
    current_task = (task_t*)kmalloc(sizeof(task_t));
	if(getLastError() != NO_ERROR)
		K_PANIC("Error allocating memory for kernel process");
	
	current_task->input_stream = create_queue(255);
	current_task->console = (virtual_console_type*)kmalloc(sizeof(virtual_console_type));
	if(getLastError() != NO_ERROR)
		K_PANIC("Error allocating space for kernel virtual console");
	cloneRealConsole(current_task->console);
	setCurrentVirtualConsole(current_task->console);
	makeVirtualConsoleFocused(current_task);
    current_task->id = next_pid++;
    current_task->esp = current_task->ebp = 0;
    current_task->eip = 0;
    current_task->page_directory = current_directory;
    //current_task->next = 0;
	current_task->state = READY;
	current_task->last_error = NO_ERROR;
	
	//ready_queue = current_task;
	add_to_list_end(current_task, task_list);
#ifdef DEBUG
	printf("Kernel process created with pid: %d\n", current_task->id);
#endif
    // Reenable interrupts.
    asm volatile("sti");
#ifdef DEBUG    
	printf("Interrupts enabled\n");
#endif
}

void move_stack(void *new_stack_start, u32int size)
{
  u32int i;
  BOOL backup = critical_section;
  // Allocate some space for the new stack.
  for( i = (u32int)new_stack_start;
       i >= ((u32int)new_stack_start-size);
       i -= 0x1000)
  {
    // General-purpose stack is in user-mode.
	if(alloc_frame( get_page(i, 1, current_directory), 0 /* User mode */, 1 /* Is writable */ ) == MEMORY_ERROR)
	{
		K_PANIC("Error allocating pages for new stack");
	}
  }
  
  // Flush the TLB by reading and writing the page directory address again.
  u32int pd_addr;
  
  
  asm volatile("mov %%cr3, %0" : "=r" (pd_addr));
  asm volatile("mov %0, %%cr3" : : "r" (pd_addr));
 
  
  // Old ESP and EBP, read from registers.
  u32int old_stack_pointer; asm volatile("mov %%esp, %0" : "=r" (old_stack_pointer));
  u32int old_base_pointer;  asm volatile("mov %%ebp, %0" : "=r" (old_base_pointer));

  // Offset to add to old stack addresses to get a new stack address.
  u32int offset            = (u32int)new_stack_start - initial_esp;

  // New ESP and EBP.
  u32int new_stack_pointer = old_stack_pointer + offset;
  u32int new_base_pointer  = old_base_pointer  + offset;

  // Copy the stack.
  memcpy((void*)new_stack_pointer, (void*)old_stack_pointer, initial_esp-old_stack_pointer);

  // Backtrace through the original stack, copying new values into
  // the new stack.  
  for(i = (u32int)new_stack_start; i > (u32int)new_stack_start-size; i -= 4)
  {
    u32int tmp = * (u32int*)i;
    // If the value of tmp is inside the range of the old stack, assume it is a base pointer
    // and remap it. This will unfortunately remap ANY value in this range, whether they are
    // base pointers or not.
    if (( old_stack_pointer < tmp) && (tmp < initial_esp))
    {
      tmp = tmp + offset;
      u32int *tmp2 = (u32int*)i;
      *tmp2 = tmp;
    }
  }

  // Change stacks.
  asm volatile("mov %0, %%esp" : : "r" (new_stack_pointer));
  asm volatile("mov %0, %%ebp" : : "r" (new_base_pointer));
  critical_section = backup;
}

void switch_task()
{
	asm volatile("cli");
    // If we haven't initialised tasking yet, just return.
    if (!current_task)
        return;
	if(!get_number_of_list_elements(task_list))
		return;
	task_t* new_task;
#ifdef DEBUG
	printf("Switching task. Current process: %d\n", current_task->id);
#endif	
    // Read esp, ebp now for saving later on.
    u32int esp, ebp, eip;
    asm volatile("mov %%esp, %0" : "=r"(esp));
    asm volatile("mov %%ebp, %0" : "=r"(ebp));
#ifdef DEBUG
	printf("Current esp: 0x%h,  ebp: 0x%h\n", esp, ebp);
#endif
    // Read the instruction pointer. We do some cunning logic here:
    // One of two things could have happened when this function exits - 
    //   (a) We called the function and it returned the EIP as requested.
    //   (b) We have just switched tasks, and because the saved EIP is essentially
    //       the instruction after read_eip(), it will seem as if read_eip has just
    //       returned.
    // In the second case we need to return immediately. To detect it we put a dummy
    // value in EAX further down at the end of this function. As C returns values in EAX,
    // it will look like the return value is this dummy value! (0x12345).
    eip = read_eip();
#ifdef DEBUG    
	printf("Received eip (line %d): 0x%h\n", __LINE__, eip);
#endif
    // Have we just switched tasks?
    if (eip == 0)
	{
#ifdef DEBUG    
		printf("\nProcess %d jumped to switch_task if magic (line: %d)\n", current_task->id, __LINE__);
#endif
        return;
	}

    //Save some register values
    current_task->eip = eip;
    current_task->esp = esp;
    current_task->ebp = ebp;
	current_task->last_error = getLastError();
#ifdef DEBUG
    printf("Saved current eip, esp, ebp values to current process (%d)\n", current_task->id);
#endif
	//--------------------------------------------------
    // Get the next task to run.
	BOOL end = false;
	new_task = get_next_from_list(&end, task_list);
	u32int n_checked = 0;
	u32int initial_size = get_number_of_list_elements(task_list);
	while((new_task?(new_task->state != READY):0) && (n_checked++ < initial_size ))
	{
		if((new_task->state == BLOCKED) && resource_is_available(&(new_task->required_resource)))
		{
			new_task->state = READY;
		}
		else if(new_task->state == KILLED)
		{
			add_to_list_end(new_task, to_be_killed);
			if((new_task = get_next_from_list(&end, task_list)) == LIST_ERROR)
				return;
		}
		else
		{
			new_task = get_next_from_list(&end, task_list);
		}
	}
	
	if(new_task == current_task)
		return;
	//--------------------------------------------------
#ifdef DEBUG
	printf("Switching to new task: %d\n", new_task->id);
#endif
    // If we fell off the end of the linked list start again at the beginning.
    
	
	setCurrentVirtualConsole(new_task->console);
    eip = new_task->eip;
    esp = new_task->esp;
    ebp = new_task->ebp;
	setLastError(new_task->last_error);
    // Make sure the memory manager knows we've changed page directory.
#ifdef DEBUG    
	printf("Change current directory (var).\nOld: 0x%h\n", current_directory->physicalAddr);
#endif
    current_directory = new_task->page_directory;
	current_task = new_task; //Change current task only before actual switch
#ifdef DEBUG    
	printf("New: 0x%h\n", current_directory->physicalAddr);
#endif
    // Here we:
    // * Stop interrupts so we don't get interrupted.
    // * Temporarily puts the new EIP location in ECX.
    // * Loads the stack and base pointers from the new task struct.
    // * Changes page directory to the physical address (physicalAddr) of the new directory.
    // * Puts a dummy value (0x12345) in EAX so that above we can recognise that we've just
    //   switched task.
    // * Restarts interrupts. The STI instruction has a delay - it doesn't take effect until after
    //   the next instruction.
    // * Jumps to the location in ECX (remember we put the new EIP in there).
#ifdef DEBUG    
	printf("Setting registers and jumping to eip:\n"
			"eip =  %h, esp = %h, ebp = %h\nAsmCal:  ", eip, esp, ebp);
#endif
	switch_jump(eip, current_directory->physicalAddr, ebp, esp);
// 	asm volatile("         \
//       mov %0, %%ecx;       \
//       mov %1, %%esp;       \
//       mov %2, %%ebp;       \
//       mov %3, %%cr3;       \
//       mov $0x12345, %%eax; \
//       jmp *%%ecx           "
//                  : : "rS"(eip), "rS"(esp), "rS"(ebp), "rS"(current_directory->physicalAddr));
}

//Interrupts should be disabled
void kill_process(task_t* task)
{
	setLastError(NO_ERROR);
	if(!task)
		return;
	u32int i, t;
	
	
	

	page_directory_t* page = task->page_directory;
	
	for(i = 0; i < 1024; i++)
	{
		if(page->tables[i])
		{
			if(kernel_directory->tables[i] != page->tables[i])
			{
				for(t = 0; t < 1024; t++)
				{
					if(page->tables[i]->pages[t].frame)
						free_frame(&page->tables[i]->pages[t]);
				}
				kfree(page->tables[i]);
				if(getLastError() != NO_ERROR)
				{
					K_PANIC(getLastErrorMessage());
				}
				printf("Freeing %d Addr: 0x%H; ", i, page->tables[i]);
			}
		}
	}
	kfree(page);
	if(getLastError() != NO_ERROR)
	{
		K_PANIC(getLastErrorMessage());
	}
	kfree(task->console);
	if(getLastError() != NO_ERROR)
	{
		K_PANIC(getLastErrorMessage());
	}
	destroy_queue(task->input_stream);
	if(getLastError() != NO_ERROR)
	{
		K_PANIC(getLastErrorMessage());
	}
	kfree(task);
	if(getLastError() != NO_ERROR)
	{
		K_PANIC(getLastErrorMessage());
	}
	//setActiveVirtualConsole(get_first_list_element(task_list)->console));
	//printf("Process %d killed\n
	return;
}

int fork()
{
    // We are modifying kernel structures, and so cannot be interrupted
    asm volatile("cli");
	BOOL backup = critical_section;
	critical_section = true;
	setLastError(NO_ERROR);
#ifdef DEBUG    
printf("Forking (interrupts disabled)...\n");
#endif
    // Take a pointer to this process' task struct for later reference.
    task_t *parent_task = (task_t*)current_task;

    // Clone the address space.
    page_directory_t *directory = clone_directory(current_directory);
	if(getLastError() != NO_ERROR)
	{
		critical_section = backup;
		return;
	}
#ifdef DEBUG    
printf("Page directory cloned: 0x%h\nCurrent page directory: 0x%h\n", directory->physicalAddr, current_directory->physicalAddr);
#endif
    // Create a new process.
    task_t *new_task = (task_t*)kmalloc(sizeof(task_t));
	memset(new_task, 0, sizeof(task_t));
	if(getLastError() != NO_ERROR)
	{
		critical_section = backup;
		return;
	}
    new_task->id = next_pid++;
    new_task->esp = new_task->ebp = 0;
    new_task->eip = 0;
    new_task->page_directory = directory;
    //new_task->next = 0;
	new_task->input_stream = create_queue(255);
	new_task->last_error = NO_ERROR;
	new_task->console = (virtual_console_type*)kmalloc(sizeof(virtual_console_type));
	if(getLastError() != NO_ERROR)
	{
		critical_section = backup;
		return;
	}
	clearVirtualConsole(new_task->console);
	new_task->state = READY;
	
#ifdef DEBUG    
printf("New task PID: %d\n", new_task->id);
#endif
    // Add it to the end of the ready queue.
//     task_t *tmp_task = (task_t*)ready_queue;
//     while (tmp_task->next)
//         tmp_task = tmp_task->next;
//     tmp_task->next = new_task;  ///////////Decide which task to choose
	add_to_list_end(new_task, task_list);
    // This will be the entry point for the new process.
    u32int eip = read_eip();
	//printf("Proc: %d, Received EIP (%s : %d): 0x%h\n:",current_task->id,  __FILE__, __LINE__, eip);

    // We could be the parent or the child here - check.
    if (current_task == parent_task)
    {
#ifdef DEBUG    
		printf("Parent process (%d) continues at %s : %d\n",current_task->id, __FILE__, __LINE__);
#endif
        // We are the parent, so set up the esp/ebp/eip for our child.
        u32int esp; asm volatile("mov %%esp, %0" : "=r"(esp));
        u32int ebp; asm volatile("mov %%ebp, %0" : "=r"(ebp));
        new_task->esp = esp;
        new_task->ebp = ebp;
        new_task->eip = eip;
#ifdef DEBUG    
		printf("Parent process saved new task (%d) regs:\n"
				"esp: 0x%h\tebp: 0x%h\teip: 0x%h\n", new_task->id, esp, ebp, eip);
#endif
        asm volatile("sti");
#ifdef DEBUG    
		printf("Interrupts enabled\n");
#endif
//return_addr = 0;
		//asm volatile("movl 4(%%ebp),%0" : "=r"(return_addr));
		//printf("Real reutrn addres: 0x%h", return_addr);
		critical_section = backup;
        return new_task->id;
    }
    else //New process initialization
    {
#ifdef DEBUG    
		u32int esp_; 
        u32int ebp_; 
		asm volatile("mov %%esp, %0" : "=r"(esp_));
		asm volatile("mov %%ebp, %0" : "=r"(ebp_));

		printf("Child prcess (%d) entered fork else (%s : %d).", current_task->id, __FILE__, __LINE__);
		printf("It's registers are equals to:\n");
#endif
		u32int eip_ = read_eip();
#ifdef DEBUG    
		printf("esp: 0x%h\tebp: 0x%h\teip: 0x%h\n",esp_, ebp_, eip_);
#endif
//         u32int return_addr = 0;// We are the child.
//         u32int i = 0;
//         //printf("Jumped to fork (else)\n");
//         return_addr = 0;
// 		printf("Stack dump:\n");
// 		asm volatile("movl %%ebp,%0" : "=r"(return_addr));
// 		return_addr -= 12;
// 		for(i = 0; i < 60; i +=4)
// 			printf("%h\n", *((u32int*)(return_addr + i)));
		//printf("Expected reutrn addres: 0x%h", return_addr);
        //globalInt += 2;
		outb(0x20, 0x20); //End of interrupt
        asm volatile("sti");//Added
#ifdef DEBUG    
		printf("Interrupts enabled");
#endif
		critical_section = backup;
        return 0;
    }

}

void initialise_resource_management()
{
	memset(res_check, 0, N_OF_RES_TYPES * sizeof(resource_check_t));
	res_check[RES_QUEUE] = queue_not_empty;
	res_check[RES_TIME] = time_has_passed;
	res_check[RES_LIST] = list_not_empty;
	//Register all resource types checkers.
}

void block_process(task_t* task, u32int res_id, u32int param)
{
	asm volatile ("cli");

	if(!res_check[res_id](param))
	{
		task->required_resource.id = res_id;
		task->required_resource.param = param;
		task->state = BLOCKED;
		raise_timer_int(); //Call switcher (scheduler)
		task->state = READY;
	}
	else
	{
		task->state = READY;
	}

	asm volatile ("sti");
	return;
}

BOOL resource_is_available(resource_type* res)
{
	return res_check?(res_check[res->id](res->param)):true;
}

static BOOL queue_not_empty(u32int queue)
{
	return !queue_is_empty((queue_type*)queue);
}

static BOOL time_has_passed(u32int tick)
{
	return timer_ticks > tick;
}

static BOOL list_not_empty(u32int list)
{
	return get_number_of_list_elements((linked_list_type*)list) > 0;
}

int getpid()
{
    return current_task->id;
}
